/*
 *   Copyright (c) 2024 Huawei Device Co., Ltd.
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

import ts from 'typescript'
import fs from 'fs'
import { HMRouterPluginConfig } from './HMRouterPluginConfig'
import {
  AnalyzerResultLike,
  HMAnimatorResult,
  HMInterceptorResult,
  HMLifecycleResult,
  HMRouterResult
} from './PluginModel'
import { HvigorLogger } from '@ohos/hvigor'
import { deleteGeneratorFile } from './Index'

class NodeInfo {
  value?: any
}

const Logger = HvigorLogger.getLogger()

export class Analyzer {
  customAnnotationExisted: boolean = false
  analyzeResultMap: Map<string, AnalyzerResultLike> = new Map()
  private sourcePath: string
  private pluginConfig: HMRouterPluginConfig
  private analyzeResult: AnalyzerResultLike = {}
  private keywordPos: number = 0

  constructor(sourcePath: string, pluginConfig: HMRouterPluginConfig) {
    this.sourcePath = sourcePath
    this.pluginConfig = pluginConfig
  }

  start() {
    const sourceCode = fs.readFileSync(this.sourcePath, 'utf-8')
    const sourceFile = ts.createSourceFile(
      this.sourcePath,
      sourceCode,
      ts.ScriptTarget.ES2021,
      false
    )
    ts.forEachChild(sourceFile, node => {
      this.resolveNode(node)
      // 解析完成后赋值存进Map
      switch (this.analyzeResult.annotation) {
        case 'HMRouter':
          this.analyzeResultMap.set(
            (this.analyzeResult as HMRouterResult).pageUrl!,
            this.analyzeResult
          )
          break
        case 'HMAnimator':
          this.analyzeResultMap.set(
            (this.analyzeResult as HMAnimatorResult).animatorName!,
            this.analyzeResult
          )
          break
        case 'HMInterceptor':
          this.analyzeResultMap.set(
            (this.analyzeResult as HMInterceptorResult).interceptorName!,
            this.analyzeResult
          )
          break
        case 'HMLifecycle':
          this.analyzeResultMap.set(
            (this.analyzeResult as HMLifecycleResult).lifecycleName!,
            this.analyzeResult
          )
          break
      }
    })
  }

  private resolveNode(node: ts.Node) {
    if (ts.isMissingDeclaration(node)) {
      this.resolveMissingDeclaration(node)
    } else if (ts.isClassDeclaration(node)) {
      this.resolveClass(node)
    } else if (ts.isDecorator(node)) {
      this.resolveDecorator(node)
    } else if (ts.isCallExpression(node)) {
      this.resolveCallExpression(node)
    } else if (ts.isExpressionStatement(node)) {
      this.resolveExpression(node)
    } else if (ts.isBlock(node)) {
      this.resolveBlock(node)
    } else if (ts.isPropertyAssignment(node)) {
      return this.resolvePropertyAccess(node)
    } else if (ts.isIdentifier(node)) {
      return this.resolveIdentifier(node)
    } else if (ts.isStringLiteral(node)) {
      return this.resolveStringLiteral(node)
    } else if (node.kind === ts.SyntaxKind.TrueKeyword) {
      let info = new NodeInfo()
      info.value = true
      return info
    } else if (node.kind === ts.SyntaxKind.FalseKeyword) {
      let info = new NodeInfo()
      info.value = false
      return info
    } else if (ts.isNumericLiteral(node)) {
      return this.resolveNumericLiteral(node)
    } else if (ts.isArrayLiteralExpression(node)) {
      let interceptors = this.resolveArrayLiteral(node)
      let info = new NodeInfo()
      info.value = interceptors
      return info
    }
  }

  private resolveMissingDeclaration(node: ts.MissingDeclaration) {
    this.analyzeResult = {}
    node.forEachChild(child => this.resolveNode(child))
  }

  private resolveClass(node: ts.ClassDeclaration) {
    // 解析到类声明，先清空一次返回结果
    this.analyzeResult = {}
    node.modifiers?.forEach(modifier => {
      // 遍历分析装饰器
      this.resolveNode(modifier)
    })
    if (this.customAnnotationExisted) {
      this.analyzeResult.name = node.name?.text
    }
  }

  private resolveDecorator(node: ts.Decorator) {
    if (ts.isCallExpression(node.expression)) {
      const callExpression = node.expression as ts.CallExpression
      if (ts.isIdentifier(callExpression.expression)) {
        this.switchIdentifier(callExpression)
      }
    }
  }

  private switchIdentifier(callExpression: ts.CallExpression) {
    const identifier = callExpression.expression as ts.Identifier
    if (this.pluginConfig.annotation.some(item => item === identifier.text)) {
      this.customAnnotationExisted = true
      // 区分是什么装饰器，构造不同的返回类
      switch (identifier.text) {
        case 'HMRouter':
          this.analyzeResult = new HMRouterResult()
          this.analyzeResult.annotation = 'HMRouter'
          break
        case 'HMAnimator':
          this.analyzeResult = new HMAnimatorResult()
          this.analyzeResult.annotation = 'HMAnimator'
          break
        case 'HMInterceptor':
          this.analyzeResult = new HMInterceptorResult()
          this.analyzeResult.annotation = 'HMInterceptor'
          break
        case 'HMLifecycle':
          this.analyzeResult = new HMLifecycleResult()
          this.analyzeResult.annotation = 'HMLifecycle'
          break
      }
      if (callExpression.arguments.length > 0) {
        this.resolveCallExpression(callExpression)
      }
    }
  }

  private resolveCallExpression(node: ts.CallExpression) {
    let identifier = this.resolveNode(node.expression)
    this.parseAnnotation(node.arguments, identifier)
  }

  private resolveExpression(node: ts.ExpressionStatement) {
    let identifier = this.resolveNode(node.expression)
    if (identifier?.value === 'struct') {
      this.keywordPos = node.end
    }
    if (this.analyzeResult.annotation === 'HMRouter' && this.keywordPos === node.pos) {
      this.analyzeResult.name = identifier?.value
    }
  }

  private resolveBlock(node: ts.Block) {
    node.statements.forEach(statement => {
      this.resolveNode(statement)
    })
  }

  private parseAnnotation(args: ts.NodeArray<ts.Expression>, nodeInfo?: NodeInfo) {
    if (this.pluginConfig.annotation.some(item => nodeInfo?.value === item)) {
      args
        .flatMap((e: ts.Expression) => (e as ts.ObjectLiteralExpression).properties)
        .forEach((e: ts.ObjectLiteralElementLike) => {
          this.parseConfig(e, this.analyzeResult)
        })
    }
  }

  private parseConfig(node: ts.ObjectLiteralElementLike, result: AnalyzerResultLike) {
    let info = this.resolveNode(node)
    Reflect.set(result, info?.value['key'], info?.value['value'])
  }

  private resolveArrayLiteral(node: ts.ArrayLiteralExpression) {
    return node.elements.map(e => this.resolveNode(e)?.value as string)
  }

  private resolvePropertyAccess(node: ts.PropertyAssignment) {
    let propertyName = this.resolveNode(node.name)?.value
    let propertyValue = this.resolveNode(node.initializer)?.value
    let info = new NodeInfo()
    info.value = { key: propertyName, value: propertyValue }
    return info
  }

  private resolveNumericLiteral(node: ts.NumericLiteral) {
    let info = new NodeInfo()
    info.value = Number(node.text)
    return info
  }

  private resolveStringLiteral(node: ts.StringLiteral) {
    let info = new NodeInfo()
    info.value = node.text
    return info
  }

  private resolveIdentifier(node: ts.Identifier) {
    if (node.escapedText === 'NavDestination' && this.analyzeResult.annotation === 'HMRouter') {
      Logger.error(
        'errorCode: 40000003, errorMsg: @HMRouter 修饰的组件不能包含 NavDestination in' +
          this.analyzeResult.name
      )
      deleteGeneratorFile(this.pluginConfig)
      throw new Error('NavDestination is not allowed in HMRouter' + this.sourcePath)
    }
    let info = new NodeInfo()
    info.value = node.escapedText.toString()
    return info
  }
}
